/************************************************************************
* texture.c
* voxelands - 3d voxel world sandbox game
* Copyright (C) Lisa 'darkrose' Milne 2016 <lisa@ltmnet.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>
************************************************************************/

#include "common.h"
#include "graphics.h"
#include "list.h"

#include <string.h>

static struct {
	texture_t *textures;
	int ids;
} tex_data = {
	NULL,
	0
};

/* generate the hardware texture */
int tex_generate(texture_t *tex)
{
	GLenum types[6] = {
		GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
		GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
		GL_TEXTURE_CUBE_MAP_POSITIVE_X,
		GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
		GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
		GL_TEXTURE_CUBE_MAP_NEGATIVE_Y
	};
	int i;
	int c;
	int b = 0;
	int t = 0;
	int m = 0;
	GLenum type;
	GLenum e;

	/* can't generate a texture if there's no data or no OpenGL */
	if (!tex || !wm_data.isinit)
		return 1;

	e = glGetError();

	/* delete the old texture, if it exists */
	if (tex->state) {
		glDeleteTextures(1, &tex->glid);
		e = glGetError();
		if (e != GL_NO_ERROR) {
			char* es = opengl_error_string(e);
			vlprintf(CN_ERROR, "%d %s",__LINE__,es);
			return 1;
		}
	}

	/* create a new texture */
	glGenTextures(1, &tex->glid);
	e = glGetError();
	if (e != GL_NO_ERROR) {
		char* es = opengl_error_string(e);
		vlprintf(CN_ERROR, "%d %s",__LINE__,es);
		return 1;
	}

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(tex->type, tex->glid);
	e = glGetError();
	if (e != GL_NO_ERROR) {
		char* es = opengl_error_string(e);
		vlprintf(CN_ERROR, "%d %s",__LINE__,es);
		return 1;
	}

	b = opengl_has_bilinear();
	t = opengl_has_trilinear();
	m = opengl_has_mipmap();

	c = 1;
	if (tex->type == GL_TEXTURE_CUBE_MAP)
		c = 6;
	for (i=0; i<c; i++) {
		if (tex->type == GL_TEXTURE_CUBE_MAP) {
			type = types[i];
		}else{
			type = GL_TEXTURE_2D;
		}
		/* draw the pixels to the texture */
		glTexImage2D(type, 0, GL_RGBA8, tex->px[i].w, tex->px[i].h , 0, GL_RGBA, GL_UNSIGNED_BYTE, tex->px[i].pixels);
		e = glGetError();
		if (e != GL_NO_ERROR) {
			char* es = opengl_error_string(e);
			vlprintf(CN_ERROR, "%d %s",__LINE__,es);
			return 1;
		}
	}

	if (m) {
		glGenerateMipmap(tex->type);
		glTexParameteri(tex->type, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(tex->type, GL_TEXTURE_WRAP_T, GL_REPEAT);
	}

	if (t) {
		glTexParameteri(tex->type,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
		if (m) {
			glTexParameteri(tex->type,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
		}else{
			glTexParameteri(tex->type, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		}
	}else if (b) {
		glTexParameteri(tex->type, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(tex->type, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	}else{
		glTexParameteri(tex->type, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(tex->type, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	}

	if (opengl_has_anisotropic()) {
		float ma = opengl_max_anisotropic();
		glTexParameterf(tex->type, GL_TEXTURE_MAX_ANISOTROPY_EXT, ma);
	}

	e = glGetError();
	if (e != GL_NO_ERROR) {
		char* es = opengl_error_string(e);
		vlprintf(CN_ERROR, "%d %s",__LINE__,es);
		return 1;
	}

	if (tex->type == GL_TEXTURE_CUBE_MAP) {
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	}

	tex->state = 1;

	return 0;
}

/* add a new image to the cache */
texture_t *tex_create()
{
	texture_t *tex = malloc(sizeof(texture_t));
	if (tex == NULL)
		return NULL;

	tex->w = 0;
	tex->h = 0;
	tex->xf = 0;
	tex->yf = 0;
	tex->name[0] = 0;
	tex->px = NULL;
	tex->id = tex_data.ids++;
	tex->glid = 0;
	tex->type = GL_TEXTURE_2D;
	tex->state = 0;

	tex_data.textures = list_push(&tex_data.textures,tex);

	return tex;
}

/* free a texture */
void tex_free(texture_t *tex)
{
	if (!tex)
		return;
	tex_data.textures = list_remove(&tex_data.textures,tex);
	if (tex->glid)
		glDeleteTextures(1, &tex->glid);
	if (tex->px && tex->type == GL_TEXTURE_CUBE_MAP)
		free(tex->px);
	free(tex);
}

/* load an image to a texture */
texture_t *tex_from_image(char* type, char* file)
{
	texture_t *t;
	/* get the image */
	image_t *p;

	t = tex_data.textures;
	while (t) {
		if (!strcmp(t->name,file))
			return t;
		t = t->next;
	}

	p = image_load(type,file);
	if (!p) {
		uint8_t r;
		uint8_t g;
		uint8_t b;
		r = math_rand_range(0,255);
		g = math_rand_range(0,255);
		b = math_rand_range(0,255);
		return tex_from_rgba(32,32,r,g,b,255);
	}

	/* and make a texture out of it */
	t = tex_from_pixels(p);
	if (t)
		strncpy(t->name,file,256);

	return t;
}

/* create a texture cube */
texture_t *tex_from_box(char* front, char* back, char* left, char* right, char* top, char* bottom)
{
	int i;
	texture_t *t;
	image_t *p[6];

	p[0] = image_load("texture",front);
	if (!p[0])
		return NULL;
	p[1] = image_load("texture",back);
	if (!p[1])
		return NULL;
	p[2] = image_load("texture",left);
	if (!p[2])
		return NULL;
	p[3] = image_load("texture",right);
	if (!p[3])
		return NULL;
	p[4] = image_load("texture",top);
	if (!p[4])
		return NULL;
	p[5] = image_load("texture",bottom);
	if (!p[5])
		return NULL;

	t = tex_create();
	if (!t)
		return NULL;

	snprintf(t->name,256,"box-%s-%s-%s-%s-%s-%s",front,back,left,right,top,bottom);

	t->type = GL_TEXTURE_CUBE_MAP;

	t->px = malloc(sizeof(image_t)*6);
	if (!t->px) {
		tex_free(t);
		return NULL;
	}

	t->w = p[0]->w;
	t->h = p[0]->h;
	t->xf = 1.0/(GLfloat)p[0]->w;
	t->yf = 1.0/(GLfloat)p[0]->h;

	for (i=0; i<6; i++) {
		t->px[i].w = p[i]->w;
		t->px[i].h = p[i]->h;
		t->px[i].pixels = p[i]->pixels;
	}

	return t;
}

/* create a texture from pixel data */
texture_t *tex_from_pixels(image_t *px)
{
	texture_t *tex;
	if (!px)
		return NULL;

	tex = tex_create();
	if (tex == NULL)
		return NULL;

	tex->w = px->w;
	tex->h = px->h;
	tex->px = px;
	tex->xf = 1.0/(GLfloat)px->w;
	tex->yf = 1.0/(GLfloat)px->h;

	if (tex_generate(tex))
		tex->state = 0;

	return tex;
}

/* update a texture from pixel data */
texture_t *tex_update_pixels(texture_t *tex, image_t *px)
{
	if (!tex)
		return tex_from_pixels(px);

	tex->w = px->w;
	tex->h = px->h ;
	tex->px = px;
	tex->xf = 1.0/(GLfloat)px->w;
	tex->yf = 1.0/(GLfloat)px->h;

	if (tex_generate(tex))
		tex->state = 0;

	tex_free(tex);

	return NULL;
}

/* create a transparent texture */
texture_t *tex_from_rgba(int x, int y, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
	char n[256];
	texture_t *t;
	image_t *p;

	snprintf(n,256,"rgba-%u-%u-%u-%u-%d-%d",r,g,b,a,x,y);

	t = tex_data.textures;
	while (t) {
		if (!strcmp(t->name,n))
			return t;
		t = t->next;
	}

	p = image_rgba(x,y,r,g,b,a);
	if (!p)
		return NULL;

	t = tex_from_pixels(p);
	strcpy(t->name,n);

	return t;
}
